Skip to content

如何实现第三方注册登录

三方登录,对于经常上网的朋友肯定不会陌生,当新访问一个网站或下载一个App,要使用里面的核心功能时,都会要求登录的。 如果这时你又没有该网站(或手机App)的账号,那就要进入繁琐的注册流程了,很多用户是比较抵触注册的,甚至嫌弃麻烦就干脆放弃使用了。

Alt text

这时候,如果网站或App能提供给三方登录的功能,就能降低用户的使用门槛。通过和已有的互联网账户建立关联,快速完成新用户的注册,让用户在无感知的流畅过程中使用服务。 这无形中也增加了获客的流量,提升了网站或应用的PV、UV等指标。

三方登录的流程

提到三方登录,不得不提一个OAuth协议了。

OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 OAUTH 的授权不会使第三方触及到用户的帐号信息(如用户名与密 码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 OAUTH 是安全的。

简而言之,OAuth协议就是实现三方登录的过程中,使用方和服务提供方共同遵守的约定。这个协议在认证和授权的时候涉及到:

  • 服务提供方,例如QQ,QQ上储存了用户的登录名,电话,昵称,头像等用户信息
  • 客户端,例如我的博客就是一个客户端,需要服务方向我提供用户的一些基本信息

OAuth 协议的认证和授权的过程如下(以QQ登录为例):

  • 用户打开我的网站后,我想要通过 QQ 获取该用户的基本信息
  • 在转跳到 QQ 的授权页面后,用户同意我获取他的基本信息
  • 网站后端获得 QQ 提供的授权码,使用该授权码向 QQ 申请一个令牌
  • QQ 对后端提供的授权码进行验证,验证无误后,发放一个令牌给后端
  • 后端使用令牌,向 QQ 获取用户信息
  • QQ 确认令牌无误,返回给后端基本的用户信息

Alt text

接入QQ登录

当你的网站想要接入QQ登录,具体要进行这么几步: 开发者注册 => 网站申请 => 网站开发 => 调用OpenAPI

  1. 开发者注册

在QQ互联开放平台首页 https://connect.qq.com/ ,点击右上角的“登录”按钮,使用QQ帐号登录,填写开发者的信息。

Alt text

  1. 网站接入申请

这一步,主要是为了获得对应的appid与appkey,以保证后续流程中可正确对网站与用户进行验证与授权。

Alt text

创建成功后,就会生成网站应用的 appidappkey

Alt text

同时也展示了有哪些权限项目可以操作。

  1. 网站开发

这一步是接入QQ登录的核心了,需要进行后台和前端的开发,以及联调测试。

Alt text

  • 前台页面首先要放置一个QQ登录的入口
  • 用户选择 QQ 登录方式,进入QQ的登录页面和授权页面
  • 用户登录成功,选择授权网站可以访问用户的信息
  • 授权通过后,跳转到网站后台
  • 网站后台,拿着授权码,置换access-token
  • 进而通过 access-token 拿到用户的基本信息 和 openID
  • 最后返回给前端页面,展示用户的昵称、头像等信息

关于 access-token 和 openID

成功登录后,即可发送请求来获取access token以及openid,这两个参数在调用OpenAPI访问和修改用户数据时必须传入,网站需自行绑定或存储: (1)access token用来判断用户在本网站上的登录状态,具有3个月有效期,用户再次登录时自动刷新。 (2)openid是此网站上唯一对应用户身份的标识,网站可将此ID进行存储便于用户下次登录时辨识其身份,或将其与用户在网站上的原有帐号进行绑定。

当用户是首次访问我们的网站,后台的数据库中还没有存储用户的关联信息,此时可以将 openID(相当于是用户的身份🆔标识) 以及基本信息存入自己的数据库中,完成首次访问的注册过程。下次再登录的时候,查询数据库,发现有关联的记录,即可直接返回给前端页面用户的基本信息。

至此,当网站的三方登录逻辑开发完成后,就可以进行上线联调了。

关键的代码逻辑

整个代码实现,包括前台页面 和 后端逻辑处理,后台我们实现NodeJS实现相关的登录逻辑。

第一步,放置QQ登录按钮

在我们的前端主页面上(这里假定为login.html)

html
 <a href="#" onclick='toLogin()'>
  <img src="img/qq_login.png">
 </a>
 <a href="#" onclick='toLogin()'>
  <img src="img/qq_login.png">
 </a>
js
 function toLogin()
 {
   //以下为按钮点击事件的逻辑。注意这里要重新打开窗口
   //否则后面跳转到QQ登录,授权页面时会直接缩小当前浏览器的窗口,而不是打开新窗口
   const childWindow = window.open("oauth/qq-login",
      "TencentLogin",
      "width=450,height=320,menubar=0,scrollbars=1,resizable=1,status=1,titlebar=0,toolbar=0,location=1"
   );
}
// 接收(从中间过渡页面传递)登录成功的消息
window.addEventListener('message', () => {
  // update view status
})
 function toLogin()
 {
   //以下为按钮点击事件的逻辑。注意这里要重新打开窗口
   //否则后面跳转到QQ登录,授权页面时会直接缩小当前浏览器的窗口,而不是打开新窗口
   const childWindow = window.open("oauth/qq-login",
      "TencentLogin",
      "width=450,height=320,menubar=0,scrollbars=1,resizable=1,status=1,titlebar=0,toolbar=0,location=1"
   );
}
// 接收(从中间过渡页面传递)登录成功的消息
window.addEventListener('message', () => {
  // update view status
})

第二步,获取code(授权码)

上一步前端页面点击,进入到后端的 controller 里面,在这儿发起三方登录的授权请求。

js
const { client_id, client_secret } = config.qqOpenAccount;
// fetch的用法可能不对,这里主要说明关键逻辑
const authorizedCode = await fetch('https://graph.qq.com/oauth2.0/authorize', {
  method: 'GET',
  data: {
    client_id,
    redirect_uri: 'http://my-server.com/qqlogin/callback',
    grant_type: 'code',
    state: 'test',
  },
});
const { client_id, client_secret } = config.qqOpenAccount;
// fetch的用法可能不对,这里主要说明关键逻辑
const authorizedCode = await fetch('https://graph.qq.com/oauth2.0/authorize', {
  method: 'GET',
  data: {
    client_id,
    redirect_uri: 'http://my-server.com/qqlogin/callback',
    grant_type: 'code',
    state: 'test',
  },
});

如果用户成功登录并授权,则会跳转到指定的回调地址,并在redirect_uri地址后带上Authorization Code和原始的state值。如:

PC网站:http://my-server.com/qqlogin/callback?code=9A5F************************06AF&state=test

第三步,获得Access Token(访问令牌)

当跳转到QQ登录页面后,用户点击“授权”按钮后,进入到回调地址(即我们的网站后台接口API)

如果用户成功登录并授权,则会跳转到指定的回调地址,并在redirect_uri地址后带上Authorization Code和原始的state值。如:

PC网站:http://my-server.com/qqlogin/callback?code=9A5F************************06AF&state=test

当回调走到我们的 controller 时,实现回调逻辑,接收授权码参数,获得访问令牌。

js
const { client_id, client_secret } = config.qqOpenAccount;

const code = ctx.query.code;
const tokenResult = await fetch('https://graph.qq.com/oauth2.0/token', {
  method: 'GET',
  data: {
    client_id,
    client_secret,
    code,
    redirect_uri: 'http://my-server.com/qqlogin/callback',
    grant_type: 'authorization_code',
    need_openid: 1
  },
});

// 如果成功返回,即可在返回包中获取到Access Token。 如(不指定fmt时):
// access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14
const { client_id, client_secret } = config.qqOpenAccount;

const code = ctx.query.code;
const tokenResult = await fetch('https://graph.qq.com/oauth2.0/token', {
  method: 'GET',
  data: {
    client_id,
    client_secret,
    code,
    redirect_uri: 'http://my-server.com/qqlogin/callback',
    grant_type: 'authorization_code',
    need_openid: 1
  },
});

// 如果成功返回,即可在返回包中获取到Access Token。 如(不指定fmt时):
// access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14

第四步,携带访问令牌(access_token),调用 openAPI的接口,获得用户的相关信息。

js
const { access_token, openID } = tokenResult.data;
const userResult = await fetch(`https://graph.qq.com/user/get_user_info?access_token=${access_token}&oauth_consumer_key=${client_id}&openid=${openID}`, {
    dataType: 'json',
});

// 成功后获得 用户信息
// {
//    "ret":0,
//    "msg":"",
//    "nickname":"YOUR_NICK_NAME",
//   ...
// }
const { access_token, openID } = tokenResult.data;
const userResult = await fetch(`https://graph.qq.com/user/get_user_info?access_token=${access_token}&oauth_consumer_key=${client_id}&openid=${openID}`, {
    dataType: 'json',
});

// 成功后获得 用户信息
// {
//    "ret":0,
//    "msg":"",
//    "nickname":"YOUR_NICK_NAME",
//   ...
// }

第五步骤,将拿到的用户信息,发送到前端页面。

在此之前,我们需要验证一下,用户之前是否注册过。如果是首次登录,需要将用户的openID及相关信息入库。

js
const user = userService.findById(openID);
if (user === null) {
  userService.save(userResult)
}
await this.ctx.render('middle.html', { user });
const user = userService.findById(openID);
if (user === null) {
  userService.save(userResult)
}
await this.ctx.render('middle.html', { user });

第六步,通知前端主页面更新用户状态

上面的 middle.html 是三方登录过程中打开一个临时过渡页面,主页面(一般为login.html或main.html)依然还存在。我们需要将登录成功的消息通知到主页面。

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>OAuth-QQ-login-success</title>
</head>
<body>
  登录中...
  <script>
    window.onload = function () {
      // 这里的 loginPage 就等于是网站前端的接收信息的页面,即主页面
      const loginPage = "https://www.my-server.com/login.html";
      window.opener.postMessage("<%= user %>", loginPage);
      window.close();
    }
  </script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>OAuth-QQ-login-success</title>
</head>
<body>
  登录中...
  <script>
    window.onload = function () {
      // 这里的 loginPage 就等于是网站前端的接收信息的页面,即主页面
      const loginPage = "https://www.my-server.com/login.html";
      window.opener.postMessage("<%= user %>", loginPage);
      window.close();
    }
  </script>
</body>
</html>

middle.html 通过 postMessage 的方式发送消息,给主登录页面 login.html。

html
<script>
window.addEventListener('message', (user) => {
  // 更新用户状态
})
</script>
<script>
window.addEventListener('message', (user) => {
  // 更新用户状态
})
</script>

至此,整个三方登录的流程就结束了。

总结

提供三方登录的服务方,大都是互联网巨头的拳头产品(如微信、微博、支付宝、Gmail等)或者是垂直行业的领导者(如github)。依托这些服务方的用户优势,可以在自己的产品中,接入他们的登录入口,获得更多的产品用户。所有的三方登录的流程都大同小异,一般都遵守OAuth协议,差别在于接口API的参数。

Alt text

三方登录的整体流程不算复杂,关键是熟悉OAuth协议,对里面的术语有所了解。同时第三方的登录注册,也可以降低网站的获客成本,可以根据实际的业务场景,接入所需的三方登录功能。

相关链接